package org.lightpersist.dao.hibernate;

import java.io.Serializable;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.util.SerializationHelper;
import org.lightcommons.util.StringUtils;
import org.lightpersist.dao.DaoTemplate;
import org.lightpersist.dao.Page;

import com.google.inject.Inject;
import com.google.inject.Provider;

public class HibernateTemplate implements DaoTemplate {

	@Inject
	private Provider<Session> sessionProvider;

	private Session getSession(){
		return sessionProvider.get();
	}
	
	public <T> void delete(T entitiy) {
		getSession().delete(entitiy);
	}

	public <T> void deleteById(Class<T> entityClass, Serializable id) {
		Session session = getSession();
		session.delete(get(entityClass, id));
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> findAll(Class<T> entityClass) {
		return findByCriteria(DetachedCriteria.forClass(entityClass));
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> findByExample(T exampleEntity) {
		return findByCriteria(getExampleCriteria(exampleEntity));
	}

	public <T> List<T> findByKeyWord(Class<T> entityClass,
			String inculdKeywords, String excludeKeywords) {
		//TODO: findByKeyWord
		throw new UnsupportedOperationException("not implement yet");
	}

	@SuppressWarnings("unchecked")
	public  List findByNamedParam(String paramedQuery,
			String[] names,Object[] values) {
		return findByNamedParam(0, -1, paramedQuery,names ,values);
	}

	@SuppressWarnings("unchecked")
	public List findByQuery(String query, Object... values) {
		return findByQuery(-1, -1, query, values);
	}

	@SuppressWarnings("unchecked")
	public List findByQuery(int startIndex, int maxResult, String query, Object... values) {
		Query queryObject = getSession().createQuery(query);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		if(startIndex>=0)
			queryObject.setFirstResult(startIndex);
		if(maxResult > 0)
			queryObject.setMaxResults(maxResult);
		return queryObject.list();
	}
	
	public int executeUpdate( String query, Object... values) {
		return executeUpdate(-1, -1, query, values);
	}
	
	public int executeUpdate(int startIndex, int maxResult, String query, Object... values) {
		Query queryObject = getSession().createQuery(query);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		if(startIndex>=0)
			queryObject.setFirstResult(startIndex);
		if(maxResult > 0)
			queryObject.setMaxResults(maxResult);
		return queryObject.executeUpdate();
	}

	@SuppressWarnings("unchecked")
	public <T> T get(Class<T> entityClass, Serializable id) {
		return (T) getSession().get(entityClass, id);
	}

	@SuppressWarnings("unchecked")
	public <T> Page<T> getPage(Class<T> entityClass, int pageIndex,
			int maxResults) {
		return getPageByCriteria(DetachedCriteria.forClass(entityClass), pageIndex, maxResults);
	}

	@SuppressWarnings("unchecked")
	public <T> Page<T> getPage(Class<T> entityClass, int pageIndex,
			int maxResults, String orderBy, boolean desc) {
		DetachedCriteria criteria =  DetachedCriteria.forClass(entityClass);
		if(desc)
			criteria.addOrder(Order.desc(orderBy));
		else
			criteria.addOrder(Order.asc(orderBy));
		return getPageByCriteria(DetachedCriteria.forClass(entityClass), pageIndex, maxResults);
	}

	@SuppressWarnings("unchecked")
	public <T> Page<T> getPageByExample(T exampleEntity, int pageIndex,
			int maxResults) {
		return getPageByCriteria(getExampleCriteria(exampleEntity), pageIndex, maxResults);
	}

	public <T> Page<T> getPageByKeyWord(Class<T> entityClass,
			String inculdKeywords, String excludeKeywords, int pageIndex,
			int maxResults) {
		// TODO Auto-generated method stub
		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> Page<T> getPageByNamedParam(String paramedQuery,
			String countQuery, int pageIndex, int maxResults,
			String[] names,Object[] values) {
		Page<T> page = new Page<T>(pageIndex,maxResults);
		List<T> objects = findByNamedParam(page.getStartIndex(), maxResults, paramedQuery,names, values);
		page.setItems(objects);
		Number count = (Number) findByNamedParam(countQuery, names,values).get(0);
		page.setTotal(count.intValue());
		return page;
	}

	@SuppressWarnings("unchecked")
	public List findByNamedParam(final int startIndex,final int maxResult,final String paramedQuery,String[] names,Object[] values){

		Query queryObject = getSession().createQuery(paramedQuery);
		if (values != null && names!=null) {
			for (int i = 0; i < names.length; i++) {
				queryObject.setParameter(names[i], values[1]);
			}
		}
		if(startIndex>=0)
			queryObject.setFirstResult(startIndex);
		if(maxResult > 0)
			queryObject.setMaxResults(maxResult);
		return queryObject.list();
	}
	
	@SuppressWarnings("unchecked")
	public <T> Page<T> getPageByQuery(String query, String countQuery,
			int pageIndex, int maxResults, Object... values) {
		int max = maxResults >0 ? maxResults : 100;
		Page<T> page = new Page<T>(pageIndex,max);
		List<T> objects = findByQuery(page.getStartIndex(), max, query, values);
		page.setItems(objects);
		if(StringUtils.hasText(countQuery)){
			Number count = (Number) findByQuery(countQuery, values).get(0);
			page.setTotal(count.intValue());
		}
		return page;
	}

	public void save(Object entity) {
		getSession().save(entity);
	}

	public void saveOrUpdate(Object entity) {
		getSession().saveOrUpdate(entity);
	}

	public void update(Object entity) {
		getSession().update(entity);
	}
	
	@SuppressWarnings("unchecked")
	public List findByCriteria(DetachedCriteria criteria) {
		return findByCriteria(criteria, 0, -1);
	}
	
	@SuppressWarnings("unchecked")
	public List findByCriteria(final DetachedCriteria criteria, final int startIndex, final int maxResults) {
		DetachedCriteria dc2 = (DetachedCriteria) SerializationHelper.clone(criteria); 
		Criteria executableCriteria = dc2.getExecutableCriteria(getSession());
		if (startIndex >= 0) {
			executableCriteria.setFirstResult(startIndex);
		}
		if (maxResults > 0) {
			executableCriteria.setMaxResults(maxResults);
		}
		return executableCriteria.list();
	}
	
	protected DetachedCriteria getExampleCriteria(Object exampleEntity){
		DetachedCriteria criteria = DetachedCriteria.forClass(exampleEntity.getClass());
		criteria.add(getExampleCriterion(exampleEntity));
		return criteria;
	}
	
	protected Criterion getExampleCriterion(Object exampleEntity){
		return Example.create(exampleEntity).enableLike(MatchMode.ANYWHERE).excludeNone().excludeZeroes();
	}
	
	@SuppressWarnings("unchecked")
	public Page getPageByCriteria(DetachedCriteria criteria,int pageIndex,
			int maxResults){
		int max = maxResults >0 ? maxResults : 100;
		Page p = new Page(pageIndex,max);
		List items = findByCriteria(criteria, p.getStartIndex(), max);
		int total = getCountOfCriteria(criteria);
		p.setItems(items);
		p.setTotal(total);
		return p;
	}
	
	public int getCountOfCriteria(final DetachedCriteria criteria) {
		DetachedCriteria dc2 = (DetachedCriteria) SerializationHelper.clone(criteria);
		Criteria crit = dc2.getExecutableCriteria(getSession());
		Integer count = (Integer) crit.setProjection(Projections.rowCount())
				.uniqueResult();
		return count==null?0:count.intValue();
	}

	public <T> List<T> findIn(Class<T> entityClass, Serializable... ids) {
		return findIn(entityClass, StringUtils.arrayToCommaDelimitedString(ids));
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> findIn(Class<T> entityClass, String commaDelimId) {
		return findByQuery("from "+entityClass.getName()+" where "
				+HibernateAnnotationUtils.getIdFieldName(entityClass)+" in ("+commaDelimId+")");
	}
}
